/*
 This file is part of ParagliderDesk.
 Copyright (C) 2009 Benjamin Loulier 
 
 ParagliderDesk is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 ParagliderDesk is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with ParagliderDesk.  If not, see <http://www.gnu.org/licenses/>.
 
 File: CLController.m
 */

#import "CLController.h"
#import "Leonardo.h"

// This is a singleton class, see below
static CLController *sharedCLDelegate = nil;

@interface CLController (utils)
- (void)addLocationToHistoric:(CLLocation *)location;
- (double)msToKmh:(double)valueInMs;
@end

@implementation CLController (utils)

- (void)addLocationToHistoric:(Location *)location {
	[location retain];
	if(locationHistoric == nil) {
		locationHistoric = [[NSMutableArray alloc] init];
		[locationHistoric addObject:location];
	}
	else { 
		//Number of locations in the historic is fixed to 2 (maybe set a prefernce for this)
		if([locationHistoric count] < 4) { 
			[locationHistoric insertObject:location atIndex:0];
		}
		else {
			[locationHistoric removeLastObject];
			[locationHistoric insertObject:location atIndex:0];
		}
	}
	[location release];
}

- (double)msToKmh:(double)valueInMs {
	return valueInMs*3.6;
}

@end

@interface CLController (getters)
- (void)getFix:(CLLocation *)newLocation;
@end

@implementation CLController (getters)

//Get type of fix (2D or 3D)
- (void)getFix:(CLLocation *)newLocation {
	//2D fix (if no 2D fix available, no 3D too)
	if (signbit(newLocation.horizontalAccuracy)) {
		// Negative accuracy means an invalid or unavailable measurement
		twoDFix = NO;
		threeDFix = NO;
	} else {
		twoDFix = YES;
		//3D fix
		if (signbit(newLocation.verticalAccuracy)) {
			threeDFix = NO;
		} else {
			threeDFix = YES;
		}
	}
}

@end

@interface CLController (calculators)
- (double) calcVerticalSpeed;
- (double) calcGlideRatio;
@end

@implementation CLController (calculators)

//Work on it to get better accuracy
- (double) calcVerticalSpeed
{
	int i;
	int count = 0;
	double speedSum = 0;
	double altDiff, during;
	double tempVerticalSpeed;
	if([locationHistoric count] > 1) {
		//Altitude difference in meters
		altDiff = [[(Location *)[locationHistoric objectAtIndex:0] coreLocationOutput] altitude] - [[(Location *)[locationHistoric objectAtIndex:1] coreLocationOutput] altitude];
		//Time in seconds
		during = [[[(Location *)[locationHistoric objectAtIndex:0] coreLocationOutput] timestamp] timeIntervalSinceDate:[[(Location *)[locationHistoric objectAtIndex:1] coreLocationOutput] timestamp]];
		[(Location *)[locationHistoric objectAtIndex:0] setVerticalSpeed:altDiff/during];
		speedSum+=altDiff/during;
		count++;
		for(i=1;i<[locationHistoric count];i++) {
			tempVerticalSpeed = [(Location *) [locationHistoric objectAtIndex:i] verticalSpeed];
			if(!signbit(tempVerticalSpeed)) {
				speedSum+=tempVerticalSpeed;
				count++;
			}
			else break;
		}
	}
	//error code for speed -1000
	if(count==0) return -1000;
	else return speedSum/count;
}

//VerticalSpeed need to be calculated before
- (double) calcGlideRatio {
	int i;
	int count = 0;
	double glideSum = 0;
	double tempVerticalSpeed;
	double tempGroundSpeed;
	tempVerticalSpeed = [[locationHistoric objectAtIndex:0] verticalSpeed];
	tempGroundSpeed = [[[locationHistoric objectAtIndex:0] coreLocationOutput] speed];
	if(tempVerticalSpeed!=-1000 && !signbit(tempGroundSpeed)) {
		//nasty trick to avoid division by zero
		if (tempVerticalSpeed == 0) tempVerticalSpeed = 0.001;
		[[locationHistoric objectAtIndex:0] setGlideRatio:tempGroundSpeed/tempVerticalSpeed];
		glideSum += tempGroundSpeed/tempVerticalSpeed;
		count++;
		for(i=1;i<[locationHistoric count];i++) {
			tempVerticalSpeed = [[locationHistoric objectAtIndex:i] verticalSpeed];
			if (tempVerticalSpeed == 0) tempVerticalSpeed = 0.001;
			tempGroundSpeed = [[[locationHistoric objectAtIndex:i] coreLocationOutput] speed];
			if(tempVerticalSpeed!=-1000 && !signbit(tempGroundSpeed)) {
				glideSum+=tempGroundSpeed/tempVerticalSpeed;
				count++;
			}
			else break;
		}		
	}
	//error code for glideratio -1000
	if(count==0) return -1000;
	else return glideSum/count;
}

@end


@implementation CLController

@synthesize delegate, locationManager, logger;
@synthesize timeElapsed;
@synthesize twoDFix, threeDFix;
@synthesize locationHistoric;

//Initialization function
- (id) init {
	self = [super init];
	if (self != nil) {
		[self setLocationManager:[[[CLLocationManager alloc] init] autorelease]]; 
		[[self locationManager] setDelegate:self]; // Tells the location manager to send updates to this object
	}
	return self;
}

// Called when the location is updated
- (void)locationManager:(CLLocationManager *)manager
	didUpdateToLocation:(CLLocation *)newLocation
		   fromLocation:(CLLocation *)oldLocation
{
	//variables made to handle previous and current Location
	Location *previousLocation, *currentLocation;
	
	// Calculate timeelapsed beetween the old point and the new point
	if ([locationHistoric count]>0) {
		previousLocation = (Location *) [locationHistoric objectAtIndex:0];
		timeElapsed = [[newLocation timestamp] timeIntervalSinceDate:[previousLocation timestamp]];
		//If timestamp of oldlocation is superior to timestamp of oldlocation we reject the new point.
		if (signbit(timeElapsed)) {
			//If the location is not valid we don't have anything to do in the fucntion, so get out
			return;
		}
	}
	
	//And we create a variable saying that newLocation is current
	currentLocation = (Location *) [[Location alloc] init:newLocation];
	//The newLocation is valid so we add it to the historic
	[self addLocationToHistoric:currentLocation];
	//We get the status of the GPS (2D or 3D fix)
	[self getFix:newLocation];
	
	//Getting datas from coreLocation
	if(twoDFix) {
		if(threeDFix) {
			[currentLocation setThreeDFix:TRUE];
			[currentLocation setVerticalSpeedAverage:[self calcVerticalSpeed]];
			[currentLocation setGlideRatioAverage:[self calcGlideRatio]];
		}
	}
	
	//Send an updtae to liveTracking class if it is started
	if([Leonardo started]) [[Leonardo sharedInstance] newLocationUpdate];
	//Send an update to the logger (the logger is not implemented for now)
	
	// Send the update to our delegate
	[self.delegate newLocationUpdate];
	[currentLocation release];
}

// Called when there is an error getting the location
- (void)locationManager:(CLLocationManager *)manager
	   didFailWithError:(NSError *)error
{
	NSMutableString *errorString = [[[NSMutableString alloc] init] autorelease];
	
	if ([error domain] == kCLErrorDomain) {
		
		// We handle CoreLocation-related errors here
		
		switch ([error code]) {
				// This error code is usually returned whenever user taps "Don't Allow" in response to
				// being told your app wants to access the current location. Once this happens, you cannot
				// attempt to get the location again until the app has quit and relaunched.
				//
				// "Don't Allow" on two successive app launches is the same as saying "never allow". The user
				// can reset this for all apps by going to Settings > General > Reset > Reset Location Warnings.
				//
			case kCLErrorDenied:
				[errorString appendFormat:@"%@\n", NSLocalizedString(@"LocationDenied", nil)];
				break;
				
				// This error code is usually returned whenever the device has no data or WiFi connectivity,
				// or when the location cannot be determined for some other reason.
				//
				// CoreLocation will keep trying, so you can keep waiting, or prompt the user.
				//
			case kCLErrorLocationUnknown:
				[errorString appendFormat:@"%@\n", NSLocalizedString(@"LocationUnknown", nil)];
				break;
				
				// We shouldn't ever get an unknown error code, but just in case...
				//
			default:
				[errorString appendFormat:@"%@ %d\n", NSLocalizedString(@"GenericLocationError", nil), [error code]];
				break;
		}
	} else {
		// We handle all non-CoreLocation errors here
		// (we depend on localizedDescription for localization)
		[errorString appendFormat:@"Error domain: \"%@\"  Error code: %d\n", [error domain], [error code]];
		[errorString appendFormat:@"Description: \"%@\"\n", [error localizedDescription]];
	}
	
	// Send the update to our delegate
	[self.delegate newError:errorString];
}

#pragma mark ---- singleton object methods ----

// See "Creating a Singleton Instance" in the Cocoa Fundamentals Guide for more info

+ (CLController *)sharedInstance {
    @synchronized(self) {
        if (sharedCLDelegate == nil) {
            [[self alloc] init]; // assignment not done here
        }
    }
    return sharedCLDelegate;
}

+ (id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (sharedCLDelegate == nil) {
            sharedCLDelegate = [super allocWithZone:zone];
            return sharedCLDelegate;  // assignment and return on first allocation
        }
    }
    return nil; // on subsequent allocation attempts return nil
}

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

- (id)retain {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    //do nothing
	[locationHistoric release];
	//[leonardo stopTracking];
	//[leonardo release];
}

- (id)autorelease {
    return self;
}

@end